/*
	file:
		FireflyView.m
	
	history:
		1993-06-06, andrew abernathy
			· created
		1993-07-04, andrew abernathy
			· added configurability
*/


#import <appkit/appkit.h>
#import "FireflyView.h"


#define DEFAULTS_OWNER	"BackSpace.Fireflies"			// this is the owner name to use for the defaults database


// this macro returns a random integer between min & max

#define randomIn(min,max)	((random () % (max - min)) + min)


// convert an NXColor to an ASCII representation

static char *	ColorToASCII (NXColor color, char * buf)
	{
	float	red, green, blue;
	
	NXConvertColorToRGB (color, &red, &green, &blue);
	sprintf (buf, "%f:%f:%f", red, green, blue);
	return buf;
	}
	

// convert an ASCII representation to an NXColor
	
static NXColor	ASCIIToColor (const char * buf)
	{
	float	red, green, blue;
	
	sscanf (buf, "%f:%f:%f", &red, &green, &blue);
	return NXConvertRGBToColor (red, green, blue);
	}


// register our defaults

static void	RegisterDefaults (void)
	{
	static	NXDefaultsVector	FireflyDefaults  =
			{
				{ "FireflyColor", "1.0:1.0:1.0" },	// fireflies default to white
				{ "FireflyCount", "60" },		// 60 fireflies initially
				{ "BackgroundColor", "0:0:0" },		// background starts at black
				{ "BoringModule", "YES" },		// yeah, we're a boring module
				{ NULL, NULL }
			};
			
	NXRegisterDefaults (DEFAULTS_OWNER, FireflyDefaults);		// register the defaults
	}
		

@implementation	FireflyView:View


/*
Here we do class initialization.  All we do right now is set up our defaults.
*/

+ initialize
	{
	RegisterDefaults ();
	return self;
	}
	

/*
Here we're notified that our inspector has been installed.  We use this for most of the initialization as we don't know of a better place; because of the way BackSpace does things, we're sure this will always get called before the screen saver module actually kicks in.  Besides, we can't make our inspector reflect our configuration data before the inspector is loaded.  If we already have some fireflies, everything must already be set up, so we don't worry about it.

Our first group of fireflies will just be wasted - never drawn.  However, this is better than checking to make sure we don't release or draw non-existant fireflies every time we get a oneStep message.
*/

- inspectorInstalled
	{
	if (!fireflies)			// no fireflies, better do setup
		{
		fireflyColor = ASCIIToColor (NXGetDefaultValue (DEFAULTS_OWNER, "FireflyColor"));
		[fireflyColorWell setColor:fireflyColor];	// update the firefly color well on the inspector
	
		fireflyCount = atoi (NXGetDefaultValue (DEFAULTS_OWNER, "FireflyCount"));
		[fireflyCountField setIntValue:fireflyCount];	// update the count field on the inspector
		fireflyCountChanged = FALSE;			// we don't have to worry about this since we set the count manually
	
		backgroundColor = ASCIIToColor (NXGetDefaultValue (DEFAULTS_OWNER, "BackgroundColor"));
		[backgroundColorWell setColor:backgroundColor];	// update the background color well on the inspector
		backgroundColorChanged = TRUE;			// so that we read the color and clear the frame in "oneStep"
	
		isBoring = strcmp (NXGetDefaultValue (DEFAULTS_OWNER, "BoringModule"), "YES") == 0;
		[isBoringSwitch setState:(isBoring ? 1 : 0)];	// update the boring module switch on the inspector
	
		fireflies = [self generateFireflies:fireflyCount];	// create some fireflies
	
		}
	
	[fireflyCountField selectText:self];			// select the firefly count so that we can quickly type to change it
	return self;
	}


/*
Here we make a single "move":  create new fireflies, draw them, then erase the old fireflies.  Erasing in the end provides the potential for erasing new fireflies, if they happen to fall in the same position as an old firefly, but this is minor, and it avoids the flicker caused by: bunch of fireflies, black, bunch of fireflies, black, etc.  Erasing old fireflies individually is much slower than just blacking everything out, but if we blacked everything out, we would get flicker.  Best would be to draw a single firefly, then erase the equivalent "old" firefly, but this is much slower due to the constant color changes.  Maybe I should be doing this off screen, but I don't know that much yet.
*/

- oneStep
	{
	int		firefly;				// firefly counter
	NXPoint *	oldFireflies;				// the old fireflies
	int		oldFireflyCount;			// number of old fireflies
	
	if (backgroundColorChanged)				// background color changed; redraw the background
		{
		backgroundColor = [backgroundColorWell color];	// get new color
		NXSetColor (backgroundColor);			// set the color to redraw the background in
		PSrectfill (0, 0, NX_WIDTH (&frame), NX_HEIGHT (&frame));
		}
		
	oldFireflies = fireflies;				// save the old fireflies
	oldFireflyCount = fireflyCount;				// save the old fireflies count
	
	if (fireflyCountChanged)				// new number of fireflies
		{
		fireflyCount = [fireflyCountField intValue];	// get new count
		fireflyCountChanged = FALSE;			// reset firefly count changed flag
		}
	fireflies = [self generateFireflies:fireflyCount];	// make new fireflies
	NXSetColor (fireflyColor);				// set the color to draw the fireflies in
	for (firefly = 0; firefly < fireflyCount; firefly++)	// draw the fireflies
		{
		PSrectfill (fireflies[firefly].x, fireflies[firefly].y, 1.0, 1.0);
		}
	
	if (!backgroundColorChanged)				// background didn't change, so we need to "black out" old fireflies
		{
		NXSetColor (backgroundColor);				// set the color to erase the old fireflies with
		for (firefly = 0; firefly < oldFireflyCount; firefly++)	// erase the old fireflies
			{
			PSrectfill (oldFireflies[firefly].x, oldFireflies[firefly].y, 1.0, 1.0);
			}
		}
	else							// background DID change...
		{
		backgroundColorChanged = FALSE;			// reset background changed flag
		}
	
	if (oldFireflies)					// got old fireflies?
		{
		free (oldFireflies);				// delete the old fireflies
		}
		
	return self;
	}


/*
We're going to get a oneStep message in a second; don't bother to draw everything, but say that we have a new background color to ensure that we clear our drawing area.
*/

- drawSelf:(const NXRect *)rects :(int)rectCount
	{
	backgroundColorChanged = TRUE;
	return self;
	}


/*
Most of our initialization is actually done in the "inspectorInstalled" method - we assume that our inspector is always pulled up once (manually or by BackSpace) before we begin executing.
*/

- initFrame:(const NXRect *)frameRect
	{
	
	[super initFrame:frameRect];
	[self allocateGState];		// For faster lock/unlockFocus
	[self setClipping:NO];		// even faster...
	srandom (time (0));		// seed random number generator
	
	return self;
	}


/*
Find our inspector panel and return it.
*/

- inspector:sender
	{
	char buf[MAXPATHLEN];

	if (!sharedInspectorPanel)	// inspector not loaded yet?  do so now.
		{
		sprintf (&buf[0], "%s/%s", [(BSThinker ()) moduleDirectory:"Firefly"], "FireflyView.nib");
		[NXApp loadNibFile:buf owner:self withNames:NO];
		}

	return sharedInspectorPanel;
	}


/*
Create some fireflies, position them, and return them for life (albeit brief) in the great outdoors.
*/

- (NXPoint *)generateFireflies:(int)numFireflies
	{
	int		firefly;
	NXPoint *	newFireflies;
	
	newFireflies = (NXPoint *) malloc (sizeof (NXPoint) * numFireflies);
	for (firefly = 0; firefly < fireflyCount; firefly++)
		{
		newFireflies[firefly].x = (NXCoord) randomIn (0, (int) NX_WIDTH (&frame));
		newFireflies[firefly].y = (NXCoord) randomIn (0, (int) NX_HEIGHT (&frame));
		}
	
	return newFireflies;
	}


/*
If we say we're boring, BackSpace won't call on us if it's in the "All" mode.
*/

- (BOOL)isBoringScreenSaver
	{
	return isBoring;
	}
	
	
/*
Movement in the background color well - let's see if the color actually changed.  If so, we note that it changed and write the new color to the defaults database.  We don't actually set the new color; that's done in the "oneStep" method, or colors get confused.
*/

- setBackgroundColor:sender
	{
	char	buf[256];
	NXColor	tempColor;
	
	tempColor = [backgroundColorWell color];
	if (!NXEqualColor (tempColor, backgroundColor))
		{
		backgroundColorChanged = TRUE;
		NXWriteDefault (DEFAULTS_OWNER, "BackgroundColor", ColorToASCII (tempColor, &buf[0]));
		}
		
	return self;
	}
	
	
/*
Movement in the firefly color well - lets see if the color actually changed.  If so, we set the new color and write the new color to the defaults database.  We go ahead and set the new firefly color here as nothing gets confused when we do.
*/

- setFireflyColor:sender
	{
	char	buf[256];
	NXColor	tempColor;
	
	tempColor = [sender color];
	if (!NXEqualColor (tempColor, fireflyColor))
		{
		fireflyColor = tempColor;
		NXWriteDefault (DEFAULTS_OWNER, "FireflyColor", ColorToASCII (fireflyColor, &buf[0]));
		}
	
	return self;
	}
	
	
/*
Movement in the firefly count field.  If the new count is negative, reset the field, as negative numbers are invalid.  Otherwise, if the new count is actually different from the old count, we note that fact and write the new value to the defaults database.  We don't actually set the new count here or we risk not erasing old fireflies or erasing fireflies that didn't exist, tromping through memory we didn't allocate and happily grabbing non-existant (and often very strange) new fireflies.  Kind of a bad thing, overall.
*/

- setFireflyCount:sender
	{
	char	buf[256];
	int	tempCount;
	
	tempCount = [sender intValue];
	if (tempCount < 0)	// don't allow negative number of fireflies
		{
		[sender setIntValue:fireflyCount];
		}
	else if (fireflyCount != tempCount)	// the count really did change?
		{
		// we don't set the count here - that'll be done in "oneStep"
		fireflyCountChanged = TRUE;
		sprintf (&buf[0], "%d", tempCount);
		NXWriteDefault (DEFAULTS_OWNER, "FireflyCount", &buf[0]);
		[fireflyCountField setIntValue:tempCount];	// update the count field in case they entered a number larger than MAXINT
		}
	
	[fireflyCountField selectText:self];
		
	return self;
	}
	
	
/*
Movement with the boring module switch; check the switch and set the corresponding flag and write the value to the defaults database.
*/

- setIsBoring:sender
	{
	switch ([sender state])
		{
		case 0:		// not set
			isBoring = FALSE;
			NXWriteDefault (DEFAULTS_OWNER, "BoringModule", "NO");
			break;
		case 1:		// IS set
			isBoring = TRUE;
			NXWriteDefault (DEFAULTS_OWNER, "BoringModule", "YES");
			break;
		default:
			// error!  should always be 0 or 1, so we ignore this case.  shouldn't ever occur, anyway.
			break;
		}
	
	return self;
	}


/*
Open the readme file for the user's reading pleasure.
*/

- openReadme:sender
	{
	char	buf[MAXPATHLEN];
	
	sprintf (&buf[0], "open %s/%s", [(BSThinker ()) moduleDirectory:"Firefly"], "Info.rtf");
	system (&buf[0]);
	return self;
	}
	
/*
Reset the configuration settings to their defaults.  A side benefit of the way I did this is that it cleans out the defaults database of values for this module, so someone removing the module from their system can easily remove all of the associated defaults.  Here we remove all of the defaults, re-register the defaults, and re-read the defaults.  We have to re-register our defaults or the act of looking one up causes a crash.  (Guess how I learned about that...)
*/

- resetSettings:sender
	{
	const char *	tempStr;
	
	// remove defaults
	NXRemoveDefault (DEFAULTS_OWNER, "FireflyColor");
	NXRemoveDefault (DEFAULTS_OWNER, "FireflyCount");
	NXRemoveDefault (DEFAULTS_OWNER, "BackgroundColor");
	NXRemoveDefault (DEFAULTS_OWNER, "BoringModule");
	
	// re-register defaults
	RegisterDefaults ();

	// re-read defaults and update inspector
	tempStr = NXGetDefaultValue (DEFAULTS_OWNER, "FireflyColor");
	if (tempStr)					// got a valid string
		{
		fireflyColor = ASCIIToColor (tempStr);
		[fireflyColorWell setColor:fireflyColor];
		}
	else
		{
		NXRunAlertPanel (0, "Could not determine firefly color", 0, 0, 0);
		}
	
	tempStr = NXGetDefaultValue (DEFAULTS_OWNER, "FireflyCount");
	if (tempStr)
		{
		[fireflyCountField setIntValue:atoi (tempStr)];
		fireflyCountChanged = TRUE;			// make sure that we read this in "oneStep"
		}
	else
		{
		NXRunAlertPanel (0, "Could not determine number of fireflies", 0, 0, 0);
		}

	tempStr = NXGetDefaultValue (DEFAULTS_OWNER, "BackgroundColor");
	if (tempStr)
		{
		[backgroundColorWell setColor:ASCIIToColor (tempStr)];
		backgroundColorChanged = TRUE;			// so that we read the color and clear the frame in "oneStep"
		}
	else
		{
		NXRunAlertPanel (0, "Could not determine background color", 0, 0, 0);
		}

	tempStr = NXGetDefaultValue (DEFAULTS_OWNER, "BoringModule");
	if (tempStr)
		{
		isBoring = strcmp (tempStr, "YES") == 0;
		[isBoringSwitch setState:(isBoring ? 1 : 0)];
		}
	else
		{
		NXRunAlertPanel (0, "Could not determine whether or not module is boring", 0, 0, 0);
		}

	[fireflyCountField selectText:self];		// select the firefly count so that we can quickly type to change it

	return self;
	}
	

@end
